home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
t3_1
/
encorsrc.lha
/
encore_sources
/
sys
/
pfloat.t
< prev
next >
Wrap
Text File
|
1988-05-02
|
17KB
|
399 lines
(herald pfloat
(env tsys))
;;; **********************************************************************
;;; This code was written as part of the Spice Lisp project at
;;; Carnegie-Mellon University, and has been placed in the public domain.
;;; Spice Lisp is currently incomplete and under active development.
;;; If you want to use this code or any part of Spice Lisp, please contact
;;; Scott Fahlman (FAHLMAN@CMUC).
;;; **********************************************************************
;;;
;;; Spice Lisp printer.
;;; Written by Neal Feinberg, Spice Lisp Group.
;;; Currently maintained by Skef Wholey.
;;;
;;; *******************************************************************
;;; *** Adapted for use in T by J. Rees, Nov 1983 ***
;;; *** Rewritten without SET by R. Kelsey, April 1986 ***
;;;; Floating Point printing
;;;
;;; Written by Bill Maddox
;;;
;;;
;;;
;;; FLONUM->STRING (and its subsidiary function FLOAT-STRING) does most of
;;; the work for all printing of floating point numbers in the printer and in
;;; FORMAT. It converts a floating point number to a string in a free or
;;; fixed format with no exponent. The interpretation of the arguments is as
;;; follows:
;;;
;;; X - The floating point number to convert, which must not be
;;; negative.
;;; WIDTH - The preferred field width, used to determine the number
;;; of fraction digits to produce if the FDIGITS parameter
;;; is unspecified or NIL. If the non-fraction digits and the
;;; decimal point alone exceed this width, no fraction digits
;;; will be produced unless a non-NIL value of FDIGITS has been
;;; specified. Field overflow is not considered an error at
;;; this level.
;;; FDIGITS - The number of fractional digits to produce. Insignificant
;;; trailing zeroes may be introduced as needed. May be
;;; unspecified or NIL, in which case as many digits as possible
;;; are generated, subject to the constraint that there are no
;;; trailing zeroes.
;;; SCALE - If this parameter is specified or non-NIL, then the number
;;; printed is (* x (expt 10 scale)). This scaling is exact,
;;; and cannot lose precision.
;;; FMIN - This parameter, if specified or non-NIL, is the minimum
;;; number of fraction digits which will be produced, regardless
;;; of the value of WIDTH or FDIGITS. This feature is used by
;;; the ~E format directive to prevent complete loss of
;;; significance in the printed value due to a bogus choice of
;;; scale factor.
;;;
;;; Most of the optional arguments are for the benefit of FORMAT and are not
;;; used by the printer.
;;;
;;; Returns:
;;; (return DIGIT-STRING DIGIT-LENGTH LEADING-POINT TRAILING-POINT DECPNT)
;;; where the results have the following interpretation:
;;;
;;; DIGIT-STRING - The decimal representation of X, with decimal point.
;;; DIGIT-LENGTH - The length of the string DIGIT-STRING.
;;; LEADING-POINT - True if the first character of DIGIT-STRING is the
;;; decimal point.
;;; TRAILING-POINT - True if the last character of DIGIT-STRING is the
;;; decimal point.
;;; POINT-POS - The position of the digit preceding the decimal
;;; point. Zero indicates point before first digit.
;;;
;;; NOTE: FLONUM->STRING goes to a lot of trouble to guarantee accuracy.
;;; Specifically, the decimal number printed is the closest possible
;;; approximation to the true value of the binary number to be printed from
;;; among all decimal representations with the same number of digits. In
;;; free-format output, i.e. with the number of digits unconstrained, it is
;;; guaranteed that all the information is preserved, so that a properly-
;;; rounding reader can reconstruct the original binary number, bit-for-bit,
;;; from its printed decimal representation. Furthermore, only as many digits
;;; as necessary to satisfy this condition will be printed.
;;;
;;;
;;; FLOAT-STRING actually generates the digits for positive numbers. The
;;; algorithm is essentially that of algorithm Dragon4 in "How to Print
;;; Floating-Point Numbers Accurately" by Steele and White. The current
;;; (draft) version of this paper may be found in [CMUC]<steele>tradix.press.
;;; DO NOT EVEN THINK OF ATTEMPTING TO UNDERSTAND THIS CODE WITHOUT READING
;;; THE PAPER!
;;; E.g. (%ceiling 27 10) => 3
(define (%ceiling x y) ; YUCKO !!
(receive (q r) (quotient&remainder x y)
(if (= r 0) q (+ q 1))))
;;; Returns a STRING-BUFFER, which must be freed at some point.
(define (flonum->string x width fdigits scale fmin)
(cond ((zero? x)
;; zero is a special case which float-string cannot handle
(let ((buffer (get-string-buffer)))
(string-writec buffer #\.)
(return buffer 1 t t 0)))
(else
(receive (#f mag exp)
(integer-decode-float x)
(float-string mag exp (integer-length mag)
width fdigits scale fmin)))))
(define (float-string fraction exponent precision width fdigits scale fmin)
(receive (r s m- m+ log-r roundup? cutoff)
(rationalize-float fraction exponent precision
width fdigits scale fmin)
(let ((buffer (get-string-buffer)))
(write-preceding-zeros log-r buffer)
(write-digits r s m- m+ log-r buffer cutoff roundup?)
(let ((decpnt (string-posq #\. buffer)))
;;add trailing zeroes to pad fraction if fdigits specified
(if fdigits
(add-asked-for-zeroes fdigits buffer decpnt))
(let ((digits (string-buffer-length buffer)))
(return buffer
digits
(fx= decpnt 0)
(fx= decpnt (fx- digits 1))
decpnt))))))
(define (add-asked-for-zeroes fdigits buffer decpnt)
(let ((needed-zeros (fx- fdigits (fx- (string-buffer-length buffer)
decpnt))))
(do ((i needed-zeros (fx- i 1)))
((fx<= i -1)) ; off by one?
(string-writec buffer #\0))))
;; Represent fraction as r/s, error bounds as m+/s and m-/s.
;; Rational arithmetic avoids loss of precision in subsequent calculations.
(define (rationalize-float fraction exponent precision width fdigits scale fmin)
(receive (r s m- m+)
(really-rationalize-float fraction exponent precision)
(receive (r s m- m+)
(scale-up r s m- m+ scale)
(receive (r s m- m+ log-r)
(compute-log-r r s m- m+)
(iterate loop ((r r) (s s) (m- m-) (m+ m+) (log-r log-r) (roundup? nil))
(receive (r s m- m+ log-r)
(recompute-log-r r s m- m+ log-r)
(let ((cutoff (get-digit-cutoff width fdigits fmin log-r)))
(receive (m- m+ new-ru?)
(if cutoff
(adjust-error m- m+ s (fx- cutoff log-r))
(return m- m+ nil))
(if (< (+ (%ash r 1) m+) (%ash s 1))
(return r s m- m+ log-r (or roundup? new-ru?) cutoff)
(loop r s m- m+ log-r (or roundup? new-ru?)))))))))))
(define (really-rationalize-float fraction exponent precision)
(receive (r s m- m+)
(cond ((fx> exponent 0)
(return (%ash fraction exponent)
1
(%ash 1 exponent)
(%ash 1 exponent)))
((fx< exponent 0)
(return fraction (%ash 1 (fx- 0 exponent)) 1 1))
(else
(return fraction 1 1 1)))
;; adjust the error bounds m+ and m- for unequal gaps
(if (= fraction (%ash 1 precision))
(return (%ash r 1) (%ash s 1) m- (%ash m+ 1))
(return r s m+ m-))))
;; scale value by requested amount, and update error bounds
(define (scale-up r s m- m+ scale)
(cond ((not scale)
(return r s m- m+))
((fx< scale 0)
(let ((scale-factor (expt 10 (fx- 0 scale))))
(return r (* s scale-factor) m- m+)))
(else
(let ((scale-factor (expt 10 scale)))
(return (* r scale-factor)
s
(* m- scale-factor)
(* m+ scale-factor))))))
(define (compute-log-r r s m- m+)
(let ((c (%ceiling s 10)))
(do ((k 0 (fx- k 1))
(d 1 (* d 10)))
((>= (* r d) c)
(return (* r d) s (* m- d) (* m+ d) k)))))
(define (recompute-log-r r s m- m+ log-r)
(let ((z (+ (%ash r 1) m+)))
(do ((s s (* s 10))
(k log-r (fx+ k 1)))
((< z (%ash s 1))
(return r s m- m+ k)))))
;;determine number of fraction digits to generate
(define (get-digit-cutoff width fdigits fmin log-r)
;;don't allow less than fmin fraction digits
(let ((fix-fmin (lambda (maybe)
(if (and fmin (fx> maybe (fx- 0 fmin)))
(fx- 0 fmin)
maybe))))
(cond (fdigits
;;use specified number of fraction digits
(fix-fmin (fx- 0 fdigits)))
((not width)
nil)
((fx< log-r 0)
;;use as many fraction digits as width will permit
(fix-fmin (fx- 1 width)))
(else
(fix-fmin (fx+ 1 (fx- log-r width)))))))
;;If we decided to cut off digit generation before precision has
;;been exhausted, rounding the last digit may cause a carry propagation.
;;We can prevent this, preserving left-to-right digit generation, with
;;a few magical adjustments to m- and m+. Of course, correct rounding
;;is also preserved.
(define (adjust-error m- m+ s count)
(let ((y (if (fx>= count 0)
(* s (expt 10 count))
(do ((i count (fx+ i 1))
(y s (%ceiling y 10)))
((fx>= i 0) y)))))
(return (max y m-) (max y m+) (>= y m+))))
;;zero-fill before fraction if no integer part
(define (write-preceding-zeros log-r buffer)
(cond ((fx>= log-r 0)
0)
(else
(string-writec buffer #\.)
(do ((i log-r (fx+ i 1)))
((fx>= i 0)
(fx- 0 log-r))
(string-writec buffer #\0)))))
;;generate the significant digits
(define (write-digits r s m- m+ log-r buffer cutoff roundup?)
(let ((ash-s-1 (%ash s 1)))
(iterate loop ((r r) (m- (* m- 10)) (m+ (* m+ 10)) (log-r (fx- log-r 1)))
(if (fx= log-r -1) (string-writec buffer #\.))
;;(multiple-value-set (u r) (truncate (* r 10) s))
(receive (u r)
(quotient&remainder (* r 10) s)
(let* ((ash-r-1 (%ash r 1))
(low (< ash-r-1 m-))
(high (if roundup?
(>= ash-r-1 (- ash-s-1 m+))
(> ash-r-1 (- ash-s-1 m+)))))
;;stop when either precision is exhausted or we have printed as many
;;fraction digits as permitted
(cond ((or low high (and cutoff (fx<= log-r cutoff)))
;;if cutoff occured before first digit, then no digits generated
;;at all. last digit may need rounding
(if (or (not cutoff) (fx>= log-r cutoff))
(string-writec buffer (last-digit u high low r s)))
;;zero-fill after integer part if no fraction
(if (fx>= log-r 0)
(add-decimal-point log-r buffer)))
(else
(string-writec buffer (digit->char u 10))
(loop r (* m- 10) (* m+ 10) (fx- log-r 1)))))))))
(define (last-digit u high low r s)
(digit->char (fx+ u (cond ((and low (not high)) 0)
((and high (not low)) 1)
((<= (%ash r 1) s) 0)
(else 1)))
10))
(define (add-decimal-point log-r buffer)
(do ((i log-r (fx- i 1)))
((fx<= i 0))
(string-writec buffer #\0))
(string-writec buffer #\.))
;;; Given a non-negative floating point number, SCALE-EXPONENT returns a
;;; new floating point number Z in the range (0.1, 1.0] and an exponent
;;; E such that Z * 10^E is (approximately) equal to the original number.
;;; There may be some loss of precision due the floating point representation.
;(defconstant short-log10-of-2 #~F0.30103s0)
(define-constant %sp-l-float fixnum->flonum)
(define-constant %long-float-ten (fixnum->flonum 10))
(define-constant %long-float-one-tenth (/ 1 %long-float-ten))
(define (log10 x) (fl/ (log x) (log %long-float-ten)))
(define-constant long-log10-of-2 (log10 (fixnum->flonum 2)))
(define-constant zero (fixnum->flonum 0))
(define (scale-exponent x)
(scale-expt-aux x (%sp-l-float 0) (%sp-l-float 1) %long-float-ten
%long-float-one-tenth long-log10-of-2))
(define (scale-expt-aux x zero one ten one-tenth log10-of-2)
(receive (#f #f exponent)
(integer-decode-float x)
(if (fl= x zero)
(return zero 1)
(let* ((e (flonum->fixnum (fl* (fixnum->flonum exponent) log10-of-2)))
(x (if (fx< e 0) ;For the end ranges.
(* (* x ten) (expt ten (fx- -1 e)))
(/ (/ x ten) (expt ten (fx- e 1))))))
(do ((d ten (* d ten))
(y x (/ x d))
(e e (fx+ 1 e)))
((< y one)
(do ((m ten (* m ten))
(z y (* z m))
(e e (fx- e 1)))
((>= z one-tenth) (return z e)))))))))
;;; Entry point for the float printer as called by PRINT, PRIN1, PRINC,
;;; etc. The argument is printed free-format, in either exponential or
;;; non-exponential notation, depending on its magnitude.
;;;
;;; NOTE: When a number is to be printed in exponential format, it is scaled
;;; in floating point. Since precision may be lost in this process, the
;;; guaranteed accuracy properties of FLONUM->STRING are lost. The
;;; difficulty is that FLONUM->STRING performs extensive computations with
;;; integers of similar magnitude to that of the number being printed. For
;;; large exponents, the bignums really get out of hand. When we switch to
;;; IEEE format for long floats, this will significantly restrict the magnitude
;;; of the largest allowable float. This combined with microcoded bignum
;;; arithmetic might make it attractive to handle exponential notation with
;;; the same accuracy as non-exponential notation, using the method described
;;; in the Steele and White paper.
(define %long-float1l-3 (/ (fixnum->flonum 1) 1000)) ;1.0e-3
(define %long-float1l7 (fixnum->flonum 10000000)) ;1.0e7
(define (print-flonum x stream) ;Entry point from PRINT
(output-float-aux x %long-float1l-3 %long-float1l7 stream))
;;; There is (was?) a TC bug lurking in here. Beware.
(define (output-float-aux x e-min e-max stream)
(cond ((nan? x) (write-string stream "NaN"))
((fl= x 0.0) (write-string stream "0.0")
;(if (not (typep x *read-default-float-format*))
; (write-string (if (typep x 'short-float) "s0" "L0")))
)
(else
(let ((x (cond ((fl< x 0.0)
(write-char stream #\-)
(fl- 0.0 x))
(else x))))
(if (and (fl>= x e-min) (fl< x e-max))
(output-free-float x stream)
(output-exponential-float x stream))))))
(define (output-free-float x stream)
(receive (str len lpoint tpoint ())
(flonum->string x nil nil nil nil)
(if lpoint (write-char stream #\0))
(write-string stream str)
(release-string-buffer str)
(if tpoint (write-char stream #\0))
;(if (not (typep x *read-default-float-format*))
; (write-string (if (typep x 'short-float) "s0" "L0")))
))
(define (output-exponential-float x stream)
(receive (f e)
(scale-exponent x)
(receive (str len lpoint tpoint ())
(flonum->string f nil nil 1 nil)
(if lpoint (write-char stream #\0))
(write-string stream str)
(release-string-buffer str)
(if tpoint (write-char stream #\0))
(write-char stream
;(if (typep x *read-default-float-format*)
#\E
;(if (typep x 'short-float) #\S #\L))
)
;; must subtract 1 from exponent here, due to
;; the scale factor of 1 in call to FLONUM->STRING
(if (not (fx< (fx- e 1) 0)) (write-char stream #\+))
(print (fx- e 1) stream))))
(set *print-flonums-kludgily?* nil)